﻿// This file is just a translation in F# of a subset of the
// "Common Utilities for the Virtualization Samples" source available on msdn:
// http://msdn.microsoft.com/en-us/library/cc723869(VS.85).aspx

module internal HyperF.Utility

open System
open System.Management


//
// For development-time only
//
let inspect (o:ManagementBaseObject) =
  for e in o.Properties do
    printfn "%s = %s" (e.Name) (if o.[e.Name] = null then "null" else string o.[e.Name])
    



type ResourceType =
| Other = 1
| ComputerSystem = 2
| Processor = 3
| Memory = 4
| IDEController = 5
| ParallelSCSIHBA = 6
| FCHBA = 7
| iSCSIHBA = 8
| IBHCA = 9
| EthernetAdapter = 10
| OtherNetworkAdapter = 11
| IOSlot = 12
| IODevice = 13
| FloppyDrive = 14
| CDDrive = 15
| DVDdrive = 16
| Serialport = 17
| Parallelport = 18
| USBController = 19
| GraphicsController = 20
| StorageExtent = 21
| Disk = 22
| Tape = 23
| OtherStorageDevice = 24
| FirewireController = 25
| PartitionableUnit = 26
| BasePartitionableUnit = 27
| PowerSupply = 28
| CoolingDevice = 29

| DisketteController = 1


(*
type ResourceSubType =
//| DisketteController = null
| DisketteDrive = "Microsoft Synthetic Diskette Drive"
| ParallelSCSIHBA = "Microsoft Synthetic SCSI Controller"
| IDEController = "Microsoft Emulated IDE Controller"
| DiskSynthetic = "Microsoft Synthetic Disk Drive"
| DiskPhysical = "Microsoft Physical Disk Drive"
| DVDPhysical = "Microsoft Physical DVD Drive"
| DVDSynthetic = "Microsoft Synthetic DVD Drive"
| CDROMPhysical = "Microsoft Physical CD Drive"
| CDROMSynthetic = "Microsoft Synthetic CD Drive"
| EthernetSynthetic = "Microsoft Synthetic Ethernet Port"
  // logical drive
| DVDLogical = "Microsoft Virtual CD/DVD Disk"
| ISOImage = "Microsoft ISO Image"
| VHD = "Microsoft Virtual Hard Disk"
| DVD = "Microsoft Virtual DVD Disk"
| VFD = "Microsoft Virtual Floppy Disk"
| videoSynthetic = "Microsoft Synthetic Display Controller"


type OtherResourceType =
| DisketteController = "Microsoft Virtual Diskette Controller"
*)


type ReturnCode =
| Completed = 0
| Started = 4096
| Failed = 32768
| AccessDenied = 32769
| NotSupported = 32770
| Unknown = 32771
| Timeout = 32772
| InvalidParameter = 32773
| SystemInUse = 32774
| InvalidState = 32775
| IncorrectDataType = 32776
| SystemNotAvailable = 32777
| OutofMemory = 32778


type JobState =
| New = 2
| Starting = 3
| Running = 4
| Suspened = 5
| ShuttingDown = 6
| Completed = 7
| Terminated = 8
| Killed = 9
| Exception = 10
| Service = 11



let getManagementScope (path:string) =
  let scope = new ManagementScope(path)

  let username =
    if Init.credentials_userName = null then null
    else if Init.credentials_domain = null then Init.credentials_userName
    else Init.credentials_domain + "\\" + Init.credentials_userName
  
  if username <> null then
    scope.Options.Impersonation <- ImpersonationLevel.Impersonate
    scope.Options.Username <- username
    scope.Options.Password <- Init.credentials_password

  scope


let setManagementObjectAuth (mo:ManagementObject) =
  let username =
    if Init.credentials_userName = null then null
    else if Init.credentials_domain = null then Init.credentials_userName
    else Init.credentials_domain + "\\" + Init.credentials_userName
  
  if username <> null then
    mo.Scope.Options.Impersonation <- ImpersonationLevel.Impersonate
    mo.Scope.Options.Username <- username
    mo.Scope.Options.Password <- Init.credentials_password

  mo


/// <summary>
/// Common utility function to get a service oject
/// </summary>
/// <param name="scope"></param>
/// <param name="serviceName"></param>
/// <returns></returns>
let getServiceObject (scope:ManagementScope) serviceName =
  scope.Connect()
  let wmiPath = new ManagementPath(serviceName)
  use serviceClass = new ManagementClass(scope, wmiPath, null)
  use services = serviceClass.GetInstances()
  use en = services.GetEnumerator()
  if en.MoveNext() then en.Current :?> ManagementObject
  else failwith "Service object not found"
    

let getTargetComputer vmElementName scope =
  let query = sprintf "select * from Msvm_ComputerSystem Where ElementName = '%s'" vmElementName
  use searcher = new ManagementObjectSearcher(scope, new ObjectQuery(query))
  use computers = searcher.Get()
  use en = computers.GetEnumerator()
  if en.MoveNext() then en.Current :?> ManagementObject
  else failwith "Target computer not found"  
  


let getVirtualSystemSetting (vm:ManagementObject) =
  use settingsData = vm.GetRelated("Msvm_VirtualSystemsettingData", "Msvm_SettingsDefineState",
                                   null, null, "SettingData", "ManagedElement", false, null)
  use en = settingsData.GetEnumerator()
  if en.MoveNext() then en.Current :?> ManagementObject
  else failwith "Failed retrieving the setting data"
    



let getSystemDevice deviceClassName (deviceObjectElementName:string) vmName scope =
  use computerSystem = getTargetComputer vmName scope
  use systemDevices = computerSystem.GetRelated(
                        deviceClassName,
                        "Msvm_SystemDevice",
                        null,
                        null,
                        "PartComponent",
                        "GroupComponent",
                        false,
                        null )

  use en = systemDevices.GetEnumerator()
  
  let mutable systemDevice = null
  while en.MoveNext() do
    if en.Current.["ElementName"].ToString().ToLower() = deviceObjectElementName.ToLower() then
      systemDevice <- en.Current
  
  systemDevice  


let getHostSystemDevice deviceClassName deviceObjectElementName scope =
  let hostName = System.Environment.MachineName
  getSystemDevice deviceClassName deviceObjectElementName hostName scope


let jobCompleted (outParams:ManagementBaseObject) scope =
  //Retrieve msvc_StorageJob path. This is a full wmi path
  let jobPath = outParams.["Job"] :?> string
  use job = new ManagementObject(scope, new ManagementPath(jobPath), null)
  //Try to get storage job information
  job.Get()
  while (job.["JobState"] :?> uint16) = uint16(JobState.Starting) || (job.["JobState"] :?> uint16) = uint16(JobState.Running) do
    //printfn "In progress... %s%% completed." (job.["PercentComplete"])
    System.Threading.Thread.Sleep(1000)
    job.Get()

  //Figure out if job failed
  //TODO: I find out that jobErrorCode=0 means 'warinig', so I should create
  // an exception type to distinguish an error from a warning
  let jobState = job.["JobState"] :?> uint16
  if jobState <> uint16(JobState.Completed) then
    let jobErrorCode = job.["ErrorCode"] :?> uint16
    failwithf "Error Code: %d, ErrorDescription: %s" jobErrorCode (string job.["ErrorDescription"])
